/*
 * TODO language 
 * Copyright (c) 2010 TODO Team
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#include <iostream>
#include <cstring>
#include <cassert>
#include "types_fwd.h"
#include "typeman/types.h"

namespace todo {

Type* TypeManager::objecttype  = NULL;
Type* TypeManager::numerictype = NULL;
Type* TypeManager::stringtype  = NULL;
Type* TypeManager::inttype     = NULL;
Type* TypeManager::doubletype  = NULL;
Type* TypeManager::booltype    = NULL;

/**
 * Data - Copy constructor
 */
Data::Data(const Data& cp)
{
	data_type = cp.data_type;
	result_flag = cp.result_flag;
	extra = cp.extra;
	data = cp.data;
	type = cp.type;
	result = cp.result;
}

void ValueResult::set_value(Value* value_)
{
	if (value) {
		value->type->dealloc(value);
	}
	value = value_;
}

ValueResult::~ValueResult()
{
	if (value) {
		value->type->dealloc(value);
	}
}

/**
 * Returns a string representation of argument list
 * @param the arguments list
 */
static inline std::string get_args_format(unsigned int num_params, Type** args)
{
	std::string ret;
	
	if (num_params == 0) {
		return std::string("void");
	}
	
	for (unsigned int i = 0; i < num_params; ++i) {
		ret += args[i]->get_name();
		if (i+1 != num_params) {
			ret += ", ";
		}
	}
	return ret;
}

/**
 * Returns a string representation of args_list
 * @param the arguments list
 */
static inline std::string get_args_list_format(unsigned int num_params, const ArgValues* args)
{
	std::string ret;
	
	if (num_params == 0) {
		return std::string("void");
	}
	
	for (unsigned int i = 0; i < num_params; ++i) {
		ret += args->args.at(i)->type->get_name();
		if (i+1 != num_params) {
			ret += ", ";
		}
	}
	return ret;
}

static inline bool check_param_types(Type** param_types, const ArgValues* passed_params)
{
	unsigned int argsize = passed_params->args.size();

	for (unsigned int i = 0; i < argsize; ++i) {
		if (param_types[i] != passed_params->args.at(i)->type) {
			return false;
		}
	}
	return true;
}

Type::~Type()
{
	for (method_map::iterator it = methods.begin(); it != methods.end(); ++it) {
		for (base_method_map::iterator it2 = (*it).second.begin(); it2 != (*it).second.end(); ++it2) {
			delete[] (*it2)->params;
			delete (*it2);
		}
	}
}

/**
 * Fetches the Value of a given property
 * @param name property's name
 */
inline Value* Value::get_property (const std::string& name)
{
	std::map<std::string, Value*>::iterator prop = properties.find(name);

	if (prop != properties.end()) {
		return prop->second;
	}
	else if (parent) {
		try {
			return parent->get_property(name);
		}
		catch (todo::Exception& e) {
			throw;
		}
	}
	else {
		throw todo::Exception((std::string("Property not found: '") + name + "'").c_str());
	}
}

/**
 * Registers all native types
 */
TypeManager::TypeManager()
{
	// Object must be the first, as it's inherited by other types
	
	objecttype  = new type::Object;
	numerictype = new type::Numeric;
	register_type("object", objecttype);
	register_type("numeric", numerictype);
	
	stringtype = new type::String;
	inttype    = new type::Int;	
	doubletype = new type::Double;
	booltype   = new type::Bool;
	register_type("vararg", new type::VarArg);
	register_type("string", stringtype);
	register_type("int", inttype);
	register_type("bool", booltype);
	register_type("double", doubletype);
	register_type("vector", new type::Vector);
	register_type("map", new type::Map);

	// Now load the types (register methods, etc)
	for (map_types::const_iterator it = types.begin(); it != types.end(); ++it) {
		it->second->load();
	}
}

/**
 * Deletes the registered types
 */
TypeManager::~TypeManager()
{
	map_types::iterator it;
	
	for (it = types.begin(); it != types.end(); ++it) {
		delete it->second;
	}
}

/**
 * Registers a type
 * @param name the type name
 * @param name the pointer to Type
 */
void TypeManager::register_type(const std::string& name, Type* new_type)
{
	if (types.find(name) != types.end()) {
		throw todo::Exception((std::string("Cannot redefine types, trying to redefine ") + name).c_str());
	}
		
	types.insert(make_pair(name, new_type));//[name] = new_type;
}

/**
 * Fetchs a type from types map
 * @param name the type name
 */
Type* TypeManager::get_type(const std::string& name)
{
	if (!std::strcmp(name.c_str(), "object" ) && objecttype  != NULL) return objecttype;
	if (!std::strcmp(name.c_str(), "numeric") && numerictype != NULL) return numerictype;
	if (!std::strcmp(name.c_str(), "string" ) && stringtype  != NULL) return stringtype;
	if (!std::strcmp(name.c_str(), "int"    ) && inttype     != NULL) return inttype;
	if (!std::strcmp(name.c_str(), "double" ) && doubletype  != NULL) return doubletype;
	if (!std::strcmp(name.c_str(), "bool"   ) && booltype    != NULL) return booltype;

	map_types::iterator type = types.find(name);
	if (type == types.end()) {
		throw todo::Exception((std::string("Type ") + name + " not types.").c_str());
	}	
	return type->second;
}

/**
 * Registers a method
 * @param method the method object representation
 */
void Type::register_method(Method* method)
{
	Method *m = find_method_ptr(method->name, const_cast<const Type**>(method->params), method->num_params, false);

	if (m != NULL)
		throw todo::Exception("Cannot redefine methods.");

	methods[method->name].push_back(method);
}

void Type::register_method(MemberFlags flags_, const std::string& name_, const std::string& ret_, MethodPtr ptr_, int n_params, Type** params, int n_required)
{
	register_method(new todo::Method(flags_, name_, ret_, ptr_, n_params, params, n_required));
}

void Type::register_method(MemberFlags flags_, const std::string& name_, Type* ret_, MethodPtr ptr_, int n_params, Type** params, int n_required)
{
	register_method(new todo::Method(flags_, name_, ret_, ptr_, n_params, params, n_required));
}

/**
 * Registers a property
 * @param n_name the property's name
 * @param n_type the property's type
 */
void Type::register_property(const std::string& n_name, Type* n_type)
{
	if (properties.find(n_name) != properties.end()) {
		throw todo::Exception("Cannot redeclare property");
	}
	properties[n_name] = n_type;
}

/**
 * Allocates the value properties.
 *
 * CAUTION: before using this function please
 * manually create the properties which refer
 * to the value type.
 *
 * @param value value to proccess
 */
void Type::alloc_value_properties(Value* value)
{
	std::map<std::string, Type*>::iterator it = properties.begin();
	std::map<std::string, Value*>::iterator vit;

	std::map<std::string, Type*>::iterator lastprop = properties.end();

	while (it != lastprop) {
		vit = value->properties.find(it->first);

		if (vit == value->properties.end())
			value->properties.insert(make_pair(it->first, it->second->alloc()));
		++it;
	}

	if (value->parent) {
		parent->alloc_value_properties(value->parent);
	}
}

bool Type::is_child_of(Type* t) const
{
	Type* p = parent;
	
	if (!p)
		return false;

	if (p == t)
		return true;

	return p->is_child_of(t);
}

/**
 * Find the pointer of the given method.
 *
  @param m_name method name
 * @param args vector of arguments to pass
 * @param recurse wheter to search the parent 
 * @return a pointer to the method or NULL
 */
Method* Type::find_method_ptr(const std::string& m_name, const ArgValues* args, bool recurse)
{
	method_map::iterator it = methods.find(m_name);
	method_map::iterator lastmethod = methods.end();

	const Type* vararg = TYPE_P("vararg");

	if (it == lastmethod && (!parent || !recurse))
		return NULL;

	if (it != lastmethod) {
		base_method_map::iterator it2;
		base_method_map::iterator lastiter = it->second.end();

		for (it2 = it->second.begin(); it2 != lastiter; ++it2) {
			Method* m = *it2;
			unsigned int i;

			if (args == NULL && m->num_params)
				continue;
			else if (args == NULL && !m->num_params)
				return m;

			unsigned int argcount = args->args.size();

			if (argcount != (unsigned)m->num_params)
				continue;

			bool foundit = true;

			while (argcount--) {
				if (m->params[argcount] == vararg) {
					foundit = true;
					break;
				}

				Value* arg = args->args.at(argcount);

				if (arg->type != m->params[argcount] && !arg->type->is_child_of(m->params[argcount])) {
					foundit = false;
					break;
				}
			}

			if (foundit)
				return m;
		}
	}

	return parent->find_method_ptr(m_name, args);
}

/**
 * Find the pointer of the given method.
 *
 * @param m_name method name
 * @param args vector of arguments to pass
 * @param count number of arguments
 * @param recurse wheter to search the parent 
 * @return a pointer to the method or NULL
 */
Method *Type::find_method_ptr (const std::string& m_name, const Type** args, const unsigned int count, bool recurse)
{
	method_map::iterator it = methods.find(m_name);
	method_map::iterator lastmethod = methods.end();

	const Type* vararg = TYPE_P("vararg");
	
	if (it == lastmethod && (!parent || !recurse))
		return NULL;

	if (it != lastmethod) {
		base_method_map::iterator it2;

		base_method_map::iterator lastiter = it->second.end();

		for (it2 = it->second.begin(); it2 != lastiter; ++it2) {
			Method* m = *it2;
			unsigned int i;

			if (count != (unsigned)m->num_params)
				continue;

			bool foundit = true;

			for (i = 0; i < count; ++i) {
				if (m->params[i] == vararg) {
					foundit = true;
					break;
				}
				if (args[i] != m->params[i] && !args[i]->is_child_of(m->params[i])) {
					foundit = false;
					break;
				}
			}

			if (foundit)
				return m;
		}
	}

	return parent->find_method_ptr(m_name, args, count);
}

std::string Type::suggest_methods (const std::string& type_name, const std::string& m_name)
{
	method_map::iterator it = methods.find(m_name);

	if (it != methods.end()) {
		std::string output;

		base_method_map::iterator it2;
		base_method_map::iterator last = it->second.end();

		for (it2 = it->second.begin(); it2 != last; it2++) {
			Method* m = (*it2);

			output += std::string("\t") + (m->flags & TODO_STATIC ? "static " : "") + type_name
				+ "::" + m_name + "(" + get_args_format(m->num_params, m->params) + ")\n";
		}

		output += parent->suggest_methods(type_name, m_name);
		return output;
	}

	if (!parent) {
		return std::string("");
	}

	return parent->suggest_methods(type_name, m_name);
}

/**
 * Call a method
 * @param m_name the function/method name
 * @param value the object instance (pass null when calling a static method)
 * @param args the arguments list
 */
Value* Type::call_method(const std::string& m_name, Value* value, const ArgValues* args)
{
	Method* method = find_method_ptr(m_name, args);

	if (method) {
		if (value && (method->flags & TODO_STATIC)) // instance given, but the method is static
			throw todo::Exception("Cannot call a static method within a non-static context.");
		else if (!value && !(method->flags & TODO_STATIC)) // no instance given to dynamic method
			throw todo::Exception("Cannot call a non-static method within a static context.");

		return (this->*(method->ptr))(value, args);
	}
	
	// Oh noes, this method does not exist. Call the firemen!... Or just suggest
	// another method :P
	
	std::string out = suggest_methods(get_name(), m_name);

	if (!out.size())
		throw todo::Exception("Method " + value->type->get_name() + "::" + m_name + "() not found.");
	else {
		std::string args_str = "void";

		if (args != NULL) {
			args_str = get_args_list_format(args->args.size(), args);
		}

		throw todo::Exception("Method " + value->type->get_name() + "::" 	+ m_name +
				"(" + args_str + ") not found, candidates are:\n"	+ out);
	}

	return NULL;
}

/**
 * Same of TypeList() but handles self references (__this__)
 */
Type** Type::type_list(unsigned int num, ...)
{
	va_list argptr;
	Type** types = new Type*[num];

	va_start(argptr, num);

	for (unsigned int i = 0; i < num; ++i) {
		const char* arg = va_arg(argptr, const char*);

		if (!std::strcmp(arg, "__this__"))
			types[i] = this;
		else
			types[i] = typeman.get_type(arg);
	}	

	va_end(argptr);

	return types;
}

/**
 * Checks if a dynamic method exists
 */
bool Type::has_method (const std::string& meth)
{
	if (methods.find(meth) != methods.end())
		return true;

	if (!parent)
		return false;

	return parent->has_method(meth);
}

/**
 * Checks if a property exists
 */
bool Type::has_property (const std::string& prop)
{
	if (properties.find(prop) != properties.end())
		return true;

	if (!parent)
		return false;

	return parent->has_property(prop);
}

} // todo
